/*
 *  CSE 40166, Computer Graphics, Fall 2013
 *
 *  PORT
 *  File: main.cpp
 *
 *  Kevin Sonnen
 *
 *  Description: Arcade style top-down video game set in space.
 *  
 *  
 *  Controls:
 *  Esc, Q, q:	Quit the program
 *  w, a, s, d:	Move the ship
 *  Click:	Teleport
 *  
 *  When running the program, pass the -v flag to generate images of each frame
 *
 */

// Include OpenGL, GLUT
#ifdef __APPLE__			// If compiling on Mac OS
	#include <GLUT/glut.h>
#else					// Else compiling on Linux OS
	#include <GL/glut.h>
	#include <GL/gl.h>
	#include <GL/glu.h>
#endif

// Include additional libraries
#include <stdio.h>
#include <math.h>
#include <string>
#include <cstring>
#include <sstream>
#include <iostream>
#include <time.h>

// Include my classes and libraries
#include "vehicle.h"
#include "ship.h"
#include "enemy.h"
#include "drawShapes.h"
#include "texture.h"

// Global variables to keep track of window width and height (in pixels)
// These may be changed when the window is resized
GLsizei windowWidth = 512;
GLsizei windowHeight = 512;

// Global variable to keep track of the original viewport size
const double origViewportSize = 512;

// Global variables to keep track of the viewport borders.
GLdouble viewportLeft;
GLdouble viewportRight;
GLdouble viewportBottom;
GLdouble viewportTop;

// Global variable to generate a video of the program.
// Enabled using "-v" command line argument when running the program.
bool makeVideoEnabled = false;

// Global array to store pixels for video output. Not used if video is not enabled.
unsigned char * pixels;

// Global ship object. This represents the player's vehicle
ship * myShip = new ship();

// Global variable to store what level the user is currently on
int levelNum = 0;

// Global array of enemy objects
int numTotalEnemies = 80;	// This is the number of enemies we want to allocate in our array
int numActiveEnemies = 0;
enemy * enemies = new enemy[numTotalEnemies];
int numEnemiesPerLevel = (levelNum*3)+1;
int numDeadEnemies;

// Global data to keep track of teleporting
bool drawTeleportLocation = false;
std::vector<float> teleportLocation;
std::vector<float> mousePosition;

// Struct to store a color vector and boolean value for a color that will change values
typedef struct changingColor {
	GLfloat color[3];
	bool increasing[2];
} changingColor;

// Store the colors and the direction of change for the multiple ports
std::vector<changingColor> portColors;

// Global radius of port
const float portRadius = 45.0f;

// Global variables for the stars texture background
const bool drawStars = true;
int starTexWidth, starTexHeight;
GLuint starTexHandle;

// Game State Enum, Global game state (What screen the user is viewing)
typedef enum {TITLE_SCREEN, MAIN_MENU, GAME, BETWEEN_LEVELS, DEAD_SCREEN, HELP_SCREEN} gameState_t;
gameState_t gameState = TITLE_SCREEN;

// Array to store which keys are pushed currently
bool keyDown[4] = {false, false, false, false};

//
// void changeSize(GLsizei w, GLsizei h)
//
// Function to handle window resize. Will be registered as GLUT's rehape callback
// Aspect ratio of objects will remain the same regardless of the window size
void changeSize( GLsizei w, GLsizei h ) {
	if (makeVideoEnabled) {
		pixels = (unsigned char *)malloc(w*h*3*sizeof(unsigned char));
		if (!pixels) {
			perror("malloc");
			abort();
		}
	}
	
	// Update the projection matrix based on the given size
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity();
	
	// Find out whether the width or height of the viewport is smaller. 
	// Make the smaller dimension equal to the original viewport size
	// Update the global variables
	if ( w <= h ) {
		viewportLeft = - origViewportSize;
		viewportRight = origViewportSize;
		viewportBottom = - origViewportSize * (GLfloat) h / (GLfloat) w;
		viewportTop = origViewportSize * (GLfloat) h / (GLfloat) w;
	} else {
		viewportLeft = - origViewportSize * (GLfloat) w / (GLfloat) h;
		viewportRight = origViewportSize * (GLfloat) w / (GLfloat) h;
		viewportBottom = - origViewportSize;
		viewportTop = origViewportSize;
	}
	
	// Adjust the viewport
	gluOrtho2D( viewportLeft, viewportRight, viewportBottom, viewportTop );
	
	// Switch back to GL_MODELVIEW mode
	glMatrixMode( GL_MODELVIEW );
	
	// Update the viewport
	glViewport( 0, 0, w, h );
	
	// Update global variables with parameters
	windowWidth = w;
	windowHeight = h;
	myShip->gameWidth = windowWidth;
	myShip->gameHeight = windowHeight;
}

/* This was used when the user could double tap a key to teleport in that direction
 Array to store if we're watching for a second click in a double click or not
 (w, s, a, d)
bool doubleClick[4] = {false, false, false, false};
void doubleClickExpiration( int value ) {
	doubleClick[value] = false;
}*/


//
// void resetEnemy(const int &index)
//
// Set the specified enemy to alive, generate a random position
//
void resetEnemy(const int &index) {
	// Set the enemy to be alive
	enemies[index].enemyState = ALIVE;
	std::vector<float> pos;
	// Generate a random angle between 0 and 2*M_PI
	float randAngle = 2*M_PI*((float) rand()/RAND_MAX);
	// Generate a random distance
	float randDistance = rand() % 110 + origViewportSize;			
	pos.push_back(randDistance*cosf(randAngle)); 
	pos.push_back(randDistance*sinf(randAngle));
	// Set the position based on the random angle and distance
	enemies[index].setPosition(pos);
	// Set vehicle speed based on the level
	enemies[index].vehicleSpeed = 4 + (0.2f*levelNum);
	// Reset explosion radius and color
	enemies[index].explosionRadius = 1.0f;
	enemies[index].explosionColor[0]=1.0;
	enemies[index].explosionColor[1]=0.0;
	enemies[index].explosionColor[2]=0.0;
	enemies[index].explosionColor[3]=1.0;
}

//
// void instantiateNewEnemy()
//
// Set a new enemy to active
//
void instantiateNewEnemy() {
	// Find a dead enemy in the array, set its position, set it to be alive
	int i = 0;
	while (enemies[i].enemyState != DEAD) {
		++i;
	}
	// i now is the index of a dead enemy we can use
	if (i<numTotalEnemies) {
		resetEnemy(i);
		++numActiveEnemies;
	}
}
//
// void initializeLevel()
//
// Called before a level starts. Sets ship, enemy positions
//
void initializeLevel() {
	std::vector<float> pos;
	pos.push_back(0);
	pos.push_back(100);
	myShip->setPosition(pos);
	std::vector<float> vel;
	vel.push_back(0);
	vel.push_back(0);
	myShip->setVelocity(vel);
	keyDown[0] = false;
	keyDown[1] = false;
	keyDown[2] = false;
	keyDown[3] = false;
	myShip->alive = true;
	
	numEnemiesPerLevel = (levelNum*3)+1;
	// Make sure the number isn't larger than the number we've allocated
	if (numEnemiesPerLevel > numTotalEnemies)
		numEnemiesPerLevel = numTotalEnemies;
	numDeadEnemies = 0;
	int initialNumActiveEnemies = levelNum + 1;
	if (initialNumActiveEnemies > numTotalEnemies)
		initialNumActiveEnemies = numTotalEnemies;
	numActiveEnemies = 0;
	
	int i;
	for (i=0; i < initialNumActiveEnemies; ++i) {
		++numActiveEnemies;
		resetEnemy(i);
	}
	// Make sure all other enemies are dead initially
	for (; i<numEnemiesPerLevel; ++i) {
		enemies[i].enemyState = DEAD;
	}
}

//
// void explosionExpiration(int i)
//
// This function is called by a timer. It means to no longer draw the exploding enemy
//
void explosionExpiration(int i) {
	if (!myShip->alive) {
		gameState = DEAD_SCREEN;
	}
	else if (numDeadEnemies >= numEnemiesPerLevel) {
		// Level finished
		gameState = BETWEEN_LEVELS;
		levelNum++;
		initializeLevel();	// Reset enemies, player
	}
	if (enemies[i].enemyState == EXPLODING)
		enemies[i].enemyState = DEAD;
}

//
// void killEnemy(const int & index);
//
// Set the enemy with the index passed to exploding, check if the level is over
//
void killEnemy ( const int & index ) {
	++numDeadEnemies;
	--numActiveEnemies;
	enemies[index].enemyState = EXPLODING;
	const float explosionDuration = 2000.0f;
	glutTimerFunc( explosionDuration, explosionExpiration, index );
}

//
// void killPlayer()
//
// Called when the player is hit by an enemy
//
void killPlayer() {
	myShip->alive = false;
	//gameState = DEAD_SCREEN;
	// Restart the level
	//initializeLevel();
}

//
// void checkEnemyCollisions
//
// Check if the specified enemy collided with the port, the player, or any of the other enemies with a smaller index
//
void checkEnemyCollisions() {
	for (int i=0; i<numEnemiesPerLevel; ++i) {
		if (enemies[i].enemyState == ALIVE) {
			std::vector<float> enemyPos = enemies[i].getPosition();
			// Check for enemy colliding with port
			if ( sqrtf(powf(enemyPos[0],2)+powf(enemyPos[1],2)) < portRadius + enemies[i].radius) {
				killEnemy(i);
			}
			// Check for enemy colliding with player
			std::vector<float> playerPos = myShip->getPosition();
			if ( sqrtf(powf(enemyPos[0]-playerPos[0],2)+powf(enemyPos[1]-playerPos[1],2)) < myShip->radius + enemies[i].radius) {
				//enemies[i].alive = false;
				killPlayer();
				killEnemy(i);
				//myShip->alive = false;
				// EXPLOSION!!!! AGHHHHH!!!!
			}
			// Check for enemies colliding with one another
			for (int j=i+1; j<numEnemiesPerLevel; ++j) {
				// Check if enemies[i] and enemies[enemyIndex] have collided.
				// If so, they're both dead.
				if (enemies[j].enemyState == ALIVE) {
					std::vector<float> otherEnemyPos = enemies[j].getPosition();
					if ( sqrtf((powf(otherEnemyPos[0]-enemyPos[0],2))+(powf(otherEnemyPos[1]-enemyPos[1],2))) < 2*enemies[i].radius) {
						killEnemy(i);
						killEnemy(j);
						// EXPLOSION!!!! AGHHHHH!!!!
					}
				}
			}
		}
	}
}

//
// void enemyTimer(int value)
//
// Timer function to instantiate a new enemy
void enemyTimer( int value ) {
	switch(gameState) {
		case GAME:
			if ( numDeadEnemies + numActiveEnemies < numEnemiesPerLevel) {
				instantiateNewEnemy();
				// Schedule a timer to call this function recursively in a duration based on the level number
				glutTimerFunc((float) 5000.0/levelNum, enemyTimer, 0);
			}
			break;
		default:
			break;
	}
	return;
}

//
// void keyboardPress (unsigned char key, int x, int y);
//
// Callback to handle input from the keyboard
//
void keyboardPress( unsigned char key, int x, int y ) {
	switch(gameState) {
		case TITLE_SCREEN:
			gameState = MAIN_MENU;
		case GAME:
			// Change position of ship
			if (key == 'w') {
				keyDown[0] = true;
				myShip->moveForward();
				/*if (doubleClick[0]) {
					myShip->teleportForward();
				} else {
					doubleClick[0] = true;
					myShip->moveForward();
					glutTimerFunc( 180.0, doubleClickExpiration, 0 );
				}*/
			} else if (key == 's') {
				keyDown[1] = true;
				myShip->moveBackward();
				/*if (doubleClick[1]) {
					myShip->teleportBackward();
				} else {
					doubleClick[1] = true;
					myShip->moveBackward();
					glutTimerFunc( 180.0, doubleClickExpiration, 1 );
				}*/
			}
			else if (key == 'a') {
				keyDown[2] = true;
				myShip->moveLeft();
				/*if (doubleClick[2]) {
					myShip->teleportLeft();
				} else {
					doubleClick[2] = true;
					myShip->moveLeft();
					glutTimerFunc( 180.0, doubleClickExpiration, 2 );
				}*/
			} else if (key == 'd') {
				keyDown[3] = true;
				myShip->moveRight();
				/*if (doubleClick[3]) {
					myShip->teleportRight();
				} else {
					doubleClick[3] = true;
					myShip->moveRight();
					glutTimerFunc( 180.0, doubleClickExpiration, 3 );
				}*/
			}
			// User used to be able to change rotation of ship
			/*else if (key == 'j') {
				myShip->rotateLeft();
			}
			else if (key == 'k') {
				myShip->rotateRight();
			}*/
			break;
		default:
			break;
	}
	// Quit the program if the Q key or Esc is pressed
	if ((key =='q') || (key == 'Q') || (key == 27)) {
		free(pixels); 		// Free allocated pixels memory
		delete[] enemies;	// Free allocated memory for enemies
		exit(0);
	}
}

void keyboardRelease( unsigned char key, int x, int y ) {
	switch(gameState) {
		case GAME:
			if (key == 'w') {
				keyDown[0] = false;
				if (!keyDown[1])
					myShip->stopVertical();
				else
					myShip->moveBackward();
			} else if (key == 's') {
				keyDown[1] = false;
				if (!keyDown[0])
					myShip->stopVertical();
				else
					myShip->moveForward();
			}
			else if (key == 'a') {
				keyDown[2] = false;
				if (!keyDown[3])
					myShip->stopHorizontal();
				else
					myShip->moveRight();
			} else if (key == 'd') {
				keyDown[3] = false;
				if (!keyDown[2])
					myShip->stopHorizontal();
				else
					myShip->moveLeft();
			}
			/*else if (key == 'j') {
				myShip->stopRotation();
			}
			else if (key == 'k') {
				myShip->stopRotation();
			}*/
			break;
		default:
			break;
	}
}

//
// void calculateTeleportLocation()
//
// Based on the x and y values of the mouse cursor, calculate where the ship would teleport to.
// The ship can only teleport a fixed distance, so we use the mouse position only to get the angle.
//
void calculateTeleportLocation() {
	// TODO: MAKE SURE TELELPORTATION LOCATION IS WITHIN SCREEN BOUNDS
	std::vector<float> shipPos = myShip->getPosition();
	float mouseDistance = sqrtf(powf((mousePosition[0]-shipPos[0]),2)+powf((mousePosition[1]-shipPos[1]),2));
	if (mouseDistance <= myShip->maxTeleportationDistance) {
		teleportLocation[0] = mousePosition[0]-shipPos[0];
		teleportLocation[1] = mousePosition[1]-shipPos[1];
	} else {
		teleportLocation[0] = (myShip->maxTeleportationDistance/mouseDistance)*(mousePosition[0]-shipPos[0]);
		teleportLocation[1] = (myShip->maxTeleportationDistance/mouseDistance)*(mousePosition[1]-shipPos[1]);
	}
	// I had troubled getting the angle to work correctly. It seems that inverse tangent only works
	// for angles in the first and fourth quadrant. So, if the angle is in either of the other two
	// quadrants, subtract M_PI. There very well may be a better way to do this.
	if (teleportLocation[0] < 0) {
		myShip->setRotation(atanf(teleportLocation[1]/teleportLocation[0])-M_PI);
	}
	else
		myShip->setRotation(atanf(teleportLocation[1]/teleportLocation[0]));
}

//
// void mouseMotion(int x, int y)
//
// Callback to handle mouse movement events
//
void mouseMotion( int x, int y ) {
	switch(gameState) {
		case MAIN_MENU:
			mousePosition[0] = (GLfloat) x*2 - windowWidth;
			mousePosition[1] = windowHeight - (GLfloat) y*2;
			break;
		case GAME:
			// TODO: Check that x, y are within gameWidth, gameHeight
			if (myShip->alive) {
				drawTeleportLocation = true;
				// Convert mouse coordinates to game coordinates
				mousePosition[0] = (GLfloat) x*2 - windowWidth;
				mousePosition[1] = windowHeight - (GLfloat) y*2;
	
				calculateTeleportLocation();
			}
			break;
		default:
			break;
	}
}

//
// void mouse(int button, int state, int x, int y)
//
// Callback to handle mouse click events
//
void mouse( int button, int state, int x, int y ) {
	if (state == GLUT_DOWN && button == GLUT_LEFT_BUTTON) {
		switch(gameState) {
			case TITLE_SCREEN:
				gameState = MAIN_MENU;
				break;
			case MAIN_MENU:
				if (mousePosition[1]) {
					if (mousePosition[1] > 0) {
						gameState = GAME;
						glutTimerFunc((float) 5000.0/levelNum, enemyTimer, 0);
					} else if (mousePosition[1] < 0) {
						gameState = HELP_SCREEN;
					}
				}
				break;
			case GAME:
				// Check if the click was within the bounds of the sun. 
				// Note: This assumes for simplicity that the window width and window height are equal
				myShip->teleportToPoint(teleportLocation[0], teleportLocation[1]);
				// Hide teleportation indicator until the mouse is moved again
				drawTeleportLocation = false;
				break;
			case BETWEEN_LEVELS:
				gameState = GAME;
				glutTimerFunc((float) 5000.0/levelNum, enemyTimer, 0);
				break;
			case DEAD_SCREEN:
				gameState = GAME;
				initializeLevel();
				glutTimerFunc((float) 5000.0/levelNum, enemyTimer, 0);
				break;
			case HELP_SCREEN:
				gameState = MAIN_MENU;
				break;
			default:
				break;
		}
	}
}

//
// void drawTeleportationPoint()
//
// Draw a circle where the calculated teleport location is, based on the position of the mouse cursor
//
void drawTeleportationPoint() {
	glPushMatrix();
	glColor4f( 0.0f, 1.0f, 1.0f, 0.5f );
	glTranslatef( myShip->getPosition()[0]+teleportLocation[0], myShip->getPosition()[1]+teleportLocation[1], 0 );
	drawCircle(20, true);
	glPopMatrix();
}

// 
// void drawShip()
//
// Draw the player's ship
//
void drawShip() {
	//glPushMatrix(); {
	if (myShip->alive)
		myShip -> draw();
	//}; glPopMatrix();
}

//
// void drawEnemies()
//
// Draw all of the live enemies, as well as all of the currently exploding enemies
//
void drawEnemies() {
	for (int i=0; i<numEnemiesPerLevel; ++i) {
		if (enemies[i].enemyState != DEAD) {
			glPushMatrix(); {
				enemies[i].draw();
			}; glPopMatrix();
		}
	}
}

//
// void drawHelpText()
//
// Draw the text that will be displayed on the help screen
//
void drawHelpText() {
	glColor3f(0.0, 1.0, 0.0);
	glRasterPos2i(-20, windowHeight/2);
	std::string s = "PORT";
	void * font = GLUT_BITMAP_9_BY_15;
	for (std::string::iterator i = s.begin(); i != s.end(); ++i) {
		char c = *i;
		glutBitmapCharacter(font, c);
	}
	
	glRasterPos2i(-(windowWidth/4), windowHeight/4);
	s = "Avoid the enemies";
	for (std::string::iterator i = s.begin(); i != s.end(); ++i) {
		char c = *i;
		glutBitmapCharacter(font, c);
	}
	
	glRasterPos2i(-(windowWidth/2)-70, 0);
	s = "Use WASD to move. Click to teleport.";
	for (std::string::iterator i = s.begin(); i != s.end(); ++i) {
		char c = *i;
		glutBitmapCharacter(font, c);
	}
	
	glRasterPos2i(-(windowWidth/2)-60, -(windowHeight/4));
	s = "There are two ways to kill enemies.";
	for (std::string::iterator i = s.begin(); i != s.end(); ++i) {
		char c = *i;
		glutBitmapCharacter(font, c);
	}
	
	glRasterPos2i(-(windowWidth/2), -(windowHeight/2));
	s = "Click to return to the menu.";
	for (std::string::iterator i = s.begin(); i != s.end(); ++i) {
		char c = *i;
		glutBitmapCharacter(font, c);
	}
}

//
// void drawDeadText()
//
// Draw the text that will be displayed on screen when the player is dead
//
void drawDeadText() {
	glColor3f(0.0, 1.0, 0.0);
	glRasterPos2i(-(windowWidth/2)-100, 0);
	std::string s = "You seem to have died. Click to continue.";
	void * font = GLUT_BITMAP_9_BY_15;
	for (std::string::iterator i = s.begin(); i != s.end(); ++i) {
		char c = *i;
		glutBitmapCharacter(font, c);
	}
}

//
// void drawLevelText()
//
// Draw the text that will be displayed when a level is finished, before moving to the next level
//
void drawLevelText() {
	glColor3f(0.0, 1.0, 0.0);
	glRasterPos2i(-(windowWidth/2)-50, 0);
	std::string dayNum;
	std::stringstream out;
	out << levelNum;
	std::string s = "End of Level " + out.str() + ". Click to continue.";
	void * font = GLUT_BITMAP_9_BY_15;
	for (std::string::iterator i = s.begin(); i != s.end(); ++i) {
		char c = *i;
		glutBitmapCharacter(font, c);
	}
}

//
// void initializePort()
//
// Initialize the colors for the multiple ports (Only needs to be called once)
//
void initializePort() {
	changingColor color1;
	portColors.push_back(color1);
	portColors[0].color[0] = 0.0;
	portColors[0].color[1] = 0.6;
	portColors[0].color[2] = 0.7;
	portColors[0].increasing[0] = false;
	portColors[0].increasing[1] = true;
	
	changingColor color2;
	portColors.push_back(color2);
	portColors[1].color[0] = 0.0;
	portColors[1].color[1] = 0.8;
	portColors[1].color[2] = 0.5;
	portColors[1].increasing[0] = true;
	portColors[1].increasing[1] = false;
	
	changingColor color3;
	portColors.push_back(color3);
	portColors[2].color[0] = 0.0;
	portColors[2].color[1] = 1.0;
	portColors[2].color[2] = 0.0;
	portColors[2].increasing[0] = false;
	portColors[2].increasing[1] = true;	
}

//
// void updatePortColors()
//
// Update the colors of the multiple ports.
// We want the ports to vary between blue and green
//
void updatePortColors() {
	const float colorDelta = 0.02f;
	for (int i=0; i<3; ++i) {
		for (int j=1; j<3; ++j) {
			if (portColors[i].color[j] >= 1.0)
				portColors[i].increasing[j-1] = false;
			else if (portColors[i].color[j] <= 0.0)
				portColors[i].increasing[j-1] = true;
			if (portColors[i].increasing[j-1])
				portColors[i].color[j] += colorDelta;
			else
				portColors[i].color[j] -= colorDelta;
		}
	}
}

//
// void drawPort(const int & x, const float & outerPortRadius)
//
// Draw the port in the center of the screen. This is done with three
// hexagons, each of which is rotating and fluctuating colors.
//
void drawPort(const int & x, const float & outerPortRadius) {
	const float portRotationSpeed = .8f;
	static float portAngle = 0.0f;
	updatePortColors();
	glPushMatrix(); {
		glTranslatef(x, 0, 0);
		glRotatef( portAngle, 0, 0, 1);
		glColor3fv(portColors[0].color);
		drawPolygon(6, outerPortRadius, false);
		glRotatef( -2*portAngle, 0, 0, 1);
		glColor3fv(portColors[1].color);
		drawPolygon(6, outerPortRadius * 0.7f, false);
		glRotatef( 2*portAngle, 0, 0, 1);
		glColor3fv(portColors[2].color);
		drawPolygon(6, outerPortRadius * 0.4f, true);
	}; glPopMatrix();
	portAngle += portRotationSpeed;
}

//
// void generateImage()
//
// Capture the current screen, store in global pixels array and then write to file
// Used if the -v command line argument is passed in
//
void generateImage() {
	static int frameno = 0;
	glReadPixels (0, 0, windowWidth, windowHeight, GL_RGB, GL_UNSIGNED_BYTE, pixels);
	char name[20];
	sprintf(name, "frame%d.ppm", frameno++);
	// Open file for writing
	FILE *fp = fopen( name, "w" );
	if (!fp) {
		perror( "fopen" );
		abort();
	}
	fprintf( fp, "P6\n%d %d\n255\n", windowWidth, windowHeight);
	// Write pixels to the file
	fwrite( pixels, windowWidth*windowHeight*3, sizeof(unsigned char), fp );
	// Close file
	fclose(fp);
}

//
// void drawStarBackground()
//
// Draw the star image texture onto a quad in the background of the game
//
void drawStarBackground() {
	// Draw white background
	/*glColor4f(1.0, 1.0, 1.0, 1.0);
	glPushMatrix(); {
		glBegin( GL_QUADS ); {
			glVertex3f( -gameWidth-10, -gameHeight-10, 0 );

			//glNormal3f( 0, 0, 1 );
			glTexCoord2f( 1, 0 );
			glVertex3f(  gameWidth+10, -gameHeight-10, 0 );

			//glNormal3f( 0, 0, 1 );
			glTexCoord2f( 1, 1 );
			glVertex3f(  gameWidth+10, gameHeight+10, 0 );
		
			//glNormal3f( 0, 0, 1 );
			glTexCoord2f( 0,  1 );
			glVertex3f( -gameWidth-10,  gameHeight+10, 0 );
		
		}; glEnd();
		glDisable( GL_TEXTURE_2D );
	}; glPopMatrix();*/
	
	// Draw stars texture on a quad
	glColor4f(1.0, 1.0, 1.0, 0.3);	// Reset the color to white before drawing image
	glPushMatrix(); {
		glEnable( GL_TEXTURE_2D );
		//glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
		glBindTexture(  GL_TEXTURE_2D, starTexHandle );
		glBegin( GL_QUADS ); {
			glNormal3f( 0, 0, 1 );
			glTexCoord2f( 0, 0 );
			glVertex3f( -windowWidth, -windowHeight, 0 );

			//glNormal3f( 0, 0, 1 );
			glTexCoord2f( 1, 0 );
			glVertex3f(  windowWidth, -windowHeight, 0 );

			//glNormal3f( 0, 0, 1 );
			glTexCoord2f( 1, 1 );
			glVertex3f(  windowWidth, windowHeight, 0 );
			
			//glNormal3f( 0, 0, 1 );
			glTexCoord2f( 0,  1 );
			glVertex3f( -windowWidth,  windowHeight, 0 );
			
		}; glEnd();
		glDisable( GL_TEXTURE_2D );
    }; glPopMatrix();
}

//
// void renderScene()
//
// Function to handle rendering. Will be registered as GLUT's display callback
//
void renderScene() {
	// Generate an image if -v option is passed
	if (makeVideoEnabled) {
		generateImage();
	}
	
	// Clear everything drawn to the screen last time by setting to black
	glClearColor( 0, 0, 0, 1 );
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	glDrawBuffer(GL_BACK);
	
	// Switch to GL_MODELVIEW
	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();
	
	// Use smooth shading, interpolate colors between points
	glShadeModel( GL_SMOOTH );
	
	// Draw different things depending on what game state we are in
	switch(gameState) {
		case TITLE_SCREEN:
			if (drawStars) {
				drawStarBackground();
			}
			drawPort(-75, 75);
			drawP(130, 200, 40, -250, 0);
			drawR(130, 200, 40, 100, 0);
			drawT(130, 200, 40, 275, 0);
			break;
		case MAIN_MENU:
			if (mousePosition[1]) {
				if (mousePosition[1] > 0) {
					// Draw a green button in the upper half of the screen
					glColor4f(0.0, mousePosition[1]/windowHeight, 0.0, 1.0);
					glBegin( GL_QUADS ); {
						// Left vertical bar windowWidth, windowHeight
						glVertex2f( -windowWidth, windowHeight );
						glVertex2f( windowWidth, windowHeight );
						glVertex2f( windowWidth, 0 );
						glVertex2f( -windowWidth, 0 );
					}; glEnd();
					glColor4f(0.0, 0.0, 0.0, 1.0f);
					drawP(140, 250, 40, -300, 250);
					drawL(140, 250, 40, -100, 250);
					drawA(140, 250, 40, 100, 250);
					drawY(140, 250, 40, 300, 250);
					glColor4f(0.0, 0.0, 1.0, 1.0);
				} else if (mousePosition[1] < 0) {
					// Draw a blue button in the lower half of the screen
					glColor4f(0.0, 0.0, -mousePosition[1]/windowHeight, 1.0);
					glBegin( GL_QUADS ); {
						// Left vertical bar windowWidth, windowHeight
						glVertex2f( -windowWidth, -windowHeight );
						glVertex2f( windowWidth, -windowHeight );
						glVertex2f( windowWidth, 0 );
						glVertex2f( -windowWidth, 0 );
					}; glEnd();
					glColor4f(0.0, 0.0, 0.0, 1.0f);
					drawH(140, 250, 40, -300, -250);
					drawE(140, 250, 40, -100, -250);
					drawL(140, 250, 40, 100, -250);
					drawP(140, 250, 40, 300, -250);
					glColor4f(0.0, 0.0, 1.0, 1.0);
					glColor4f(0.0, 1.0, 0.0, 1.0);
				}
			}
			drawP(80, 120, 25, -180, 0);
			drawR(80, 120, 25, 70, 0);
			drawT(80, 120, 25, 200, 0);
			drawPort(-55, 50);
			break;
		case GAME:	
			if (drawStars) {
				drawStarBackground();
			}
			
			// If we turn on the boolean moveCamera flag in ship.cpp:
			// Anything drawn before the ship will remain static with respect to the camera (e.g. HUD)
			// Anything drawn after the ship will move with respect to the camera
			drawShip();
			drawPort(0, portRadius+5);
			if (drawTeleportLocation && myShip->alive)
				drawTeleportationPoint();
			drawEnemies();
			break;
		case BETWEEN_LEVELS:
			drawLevelText();
			break;
		case DEAD_SCREEN:
			drawDeadText();
			break;
		case HELP_SCREEN:
			drawHelpText();
			break;
		default:
			break;
	}
	
    	// Flush the output using double buffering to swap the front and back buffers
    	glutSwapBuffers();
}

//
// void updateTimer(int value)
//
// Timer function to update the positions of objects in the world
void updateTimer( int value ) {
	switch(gameState) {
		case GAME:
			if (myShip->alive) {
				// Update positions if velocity is not 0
				if (myShip->getVelocity()[0] || myShip->getVelocity()[1]) {
					myShip->updatePosition();
					calculateTeleportLocation();	// Update the teleport cursor location after ship has moved
				}
				for (int i=0; i<numEnemiesPerLevel; ++i) {
					if (enemies[i].enemyState == ALIVE) {
						enemies[i].updatePosition( *myShip );
					}
				}
				checkEnemyCollisions();
			}
			break;
		default:
			break;
	}
	
	// Call the redisplay event
	glutPostRedisplay();
	
	// Schedule a timer to call this function recursively in 33.3 ms
	glutTimerFunc(33.3, updateTimer, 0);
	return;
}

//
// int main(int argc, char* argv[])
//
int main( int argc, char* argv[] ) {
	// Seed the random number generator
	srand((unsigned) time(NULL));
	teleportLocation.push_back(0);
	teleportLocation.push_back(0);
	mousePosition.push_back(0);
	mousePosition.push_back(0);
	initializePort();
	initializeLevel();
	//initializeEnemies();
	
	// Check for -v command line argument. If found, enable video output.
	for( int count=1; count < argc; count++ ) {
		if( std::strcmp(argv[count], "-v") == 0 ) {
			makeVideoEnabled = true;
			//std::cout << "Enabling video output" << std::endl;
		}
	}
	
	// Initialize GLUT
	glutInit( &argc, argv );
	
	// Initialize display with RGB double frame buffer
	glutInitDisplayMode( GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB );
	// Place display at (50, 50)
	glutInitWindowPosition( 50, 50 );
	// Set size to global size variables
	glutInitWindowSize( windowWidth, windowHeight );
	
	// Control how OpenGL stores pixels in memory if we're generating images
	if( makeVideoEnabled ) {
		glPixelStorei( GL_PACK_ROW_LENGTH, 0 );
		glPixelStorei( GL_PACK_SKIP_PIXELS, 0 );
		glPixelStorei( GL_PACK_ALIGNMENT, 1 );
	}
	
	// Create Window with title
	glutCreateWindow( "PORT" );
	
	// Enable alpha
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
	glEnable (GL_BLEND);
	
	// Register reshape and display callbacks
	glutDisplayFunc( renderScene );
	glutReshapeFunc( changeSize );
	
	// Schedule a timer to run a function in 33.3 ms for updating the display
	glutTimerFunc( 33.3, updateTimer, 0 );
	
	// Disable Key repeat rate for better controls
	glutSetKeyRepeat( GLUT_KEY_REPEAT_OFF );
	// Register keyboard callbacks for both press and release
	glutKeyboardFunc( keyboardPress );
	glutKeyboardUpFunc( keyboardRelease );
	
	// Register mouse callback
	glutPassiveMotionFunc( mouseMotion );
	glutMouseFunc( mouse );
	
	// Load the stars texture into starTexData
	if (drawStars) {
		// Read in stars.ppm
		unsigned char *starTexData;
		if( readPPM( "starTexture.ppm", starTexWidth, starTexHeight, starTexData ) )
			printf( "Successfully read in image stars.ppm\n" );
		if( registerOpenGLTexture( starTexData, starTexWidth, starTexHeight, starTexHandle ) )
			printf( "Successfully registered texture with handle %d\n", starTexHandle );
	}
		
	// Begin GLUT loop
	glutMainLoop();
	
	// Never reached
	return 0;
}
